//------------------------------------------------------------------------------
//  shadowserver.cc
//  (C) 2007 Radon Labs GmbH
//------------------------------------------------------------------------------
#include "stdneb.h"
#include "lighting/shadowserver.h"
#include "frame/frameserver.h"
#include "coregraphics/transformdevice.h"
#include "models/visresolver.h"
#include "graphics/modelentity.h"

namespace Lighting
{
ImplementClass(Lighting::ShadowServer, 'SHDS', Core::RefCounted);
ImplementSingleton(Lighting::ShadowServer);

using namespace Math;
using namespace Util;
using namespace Frame;
using namespace Resources;
using namespace CoreGraphics;
using namespace Graphics;
using namespace Models;

//------------------------------------------------------------------------------
/**
*/
ShadowServer::ShadowServer() :
    isOpen(false),
    inBeginFrame(false),
    inBeginAttach(false)
{
    ConstructSingleton;
}

//------------------------------------------------------------------------------
/**
*/
ShadowServer::~ShadowServer()
{
    n_assert(!this->isOpen);
    n_assert(!this->inBeginFrame);
    n_assert(!this->inBeginAttach);
    DestructSingleton;
}

//------------------------------------------------------------------------------
/**
*/
void
ShadowServer::Open()
{
    n_assert(!this->isOpen);
    this->isOpen = true;

    // load the ShadowBuffer frame shader
    FrameServer* frameServer = FrameServer::Instance();
    if (!frameServer->HasFrameShader(ResourceId("VSMShadowMapping")))
    {
        n_error("ShadowServer::Open(): Frame shader 'VSMShadowMapping' not found!");
    }
    this->frameShader = frameServer->GetFrameShaderByName(ResourceId("VSMShadowMapping"));
    n_assert(this->frameShader.isvalid());
    this->finalPostEffect = this->frameShader->GetPostEffectByName(ResourceId("Final"));

    // setup the shadow buffer render target, this is a single
    // render target which contains the shadow buffer data for
    // all shadow casting light sources
    const Ptr<RenderTarget>& origRt = this->frameShader->GetRenderTargetByName(ResourceId("VSMDownscale"));
    SizeT rtWidth = origRt->GetWidth();
    SizeT rtHeight = origRt->GetHeight();
    SizeT resolveWidth = rtWidth * MaxNumShadowLights;
    SizeT resolveHeight = rtHeight;
    PixelFormat::Code pixelFormat = origRt->GetColorBufferFormat(0);
    
    this->shadowBuffer = RenderTarget::Create();
    this->shadowBuffer->SetWidth(rtWidth);
    this->shadowBuffer->SetHeight(rtHeight);
    this->shadowBuffer->SetAntiAliasQuality(AntiAliasQuality::None);
    this->shadowBuffer->AddColorBuffer(pixelFormat);
    this->shadowBuffer->SetMipMapsEnabled(true);
    this->shadowBuffer->SetResolveTextureResourceId(ResourceId("ShadowBuffer"));
    this->shadowBuffer->SetResolveTextureWidth(resolveWidth);
    this->shadowBuffer->SetResolveTextureHeight(resolveHeight);
    this->shadowBuffer->Setup();
}

//------------------------------------------------------------------------------
/**
*/
void
ShadowServer::Close()
{
    n_assert(this->isOpen);
    n_assert(!this->inBeginFrame);
    n_assert(!this->inBeginAttach);
    this->isOpen = false;

    // release shadow buffer
    this->shadowBuffer->Discard();
    this->shadowBuffer = 0;

    // release frame shader
    this->finalPostEffect = 0;
    this->frameShader->Discard();
    this->frameShader = 0;
}

//------------------------------------------------------------------------------
/**
*/
void
ShadowServer::BeginFrame()
{
    n_assert(this->isOpen);
    n_assert(!this->inBeginFrame);
    n_assert(!this->inBeginAttach);
    n_assert(this->visibleLightEntities.IsEmpty());
    this->inBeginFrame = true;
}

//------------------------------------------------------------------------------
/**
*/
void
ShadowServer::EndFrame()
{
    n_assert(this->inBeginFrame);
    n_assert(!this->inBeginAttach);
    this->inBeginFrame = false;
    this->visibleLightEntities.Clear();
}

//------------------------------------------------------------------------------
/**
*/
void
ShadowServer::BeginAttachVisibleLights()
{
    n_assert(this->inBeginFrame);
    n_assert(!this->inBeginAttach);
    n_assert(this->visibleLightEntities.IsEmpty());
    this->inBeginAttach = true;
}

//------------------------------------------------------------------------------
/**
*/
void
ShadowServer::AttachVisibleLight(const Ptr<AbstractLightEntity>& lightEntity)
{
    n_assert(lightEntity->GetCastShadows());
    this->visibleLightEntities.Append(lightEntity);
}

//------------------------------------------------------------------------------
/**
*/
void
ShadowServer::EndAttachVisibleLights()
{
    n_assert(this->inBeginAttach);
    this->inBeginAttach = false;

    // @todo: sort shadow casting light sources by priority
}

//------------------------------------------------------------------------------
/**
    This method updates the shadow buffer for each visible shadow 
    casting light by performing a rendering pass per light source which
    render all ModelEntities influenced by the light into a shadow buffer.
*/
void
ShadowServer::UpdateShadowBuffer()
{
    n_assert(this->inBeginFrame);
    n_assert(!this->inBeginAttach);
    TransformDevice* transDev = TransformDevice::Instance();
    VisResolver* visResolver = VisResolver::Instance();

    // backup current view and projection matrices
    matrix44 origView = transDev->GetViewTransform();
    matrix44 origProj = transDev->GetProjTransform();

    // for each shadow casting light...
    SizeT numLights = this->visibleLightEntities.Size();
    if (numLights > MaxNumShadowLights)
    {
        numLights = MaxNumShadowLights;
    }
    IndexT lightIndex;
    for (lightIndex = 0; lightIndex < numLights; lightIndex++)
    {
        // render shadow casters in current light volume to shadow buffer
        const Ptr<AbstractLightEntity>& lightEntity = this->visibleLightEntities[lightIndex];
        transDev->SetViewTransform(lightEntity->GetInvTransform());
        transDev->SetProjTransform(lightEntity->GetProjTransform());

        // perform visibility resolve for current light
        visResolver->BeginResolve();
        const Array<Ptr<GraphicsEntity>>& visLinks = lightEntity->GetLinks(GraphicsEntity::LightLink);
        IndexT linkIndex;
        for (linkIndex = 0; linkIndex < visLinks.Size(); linkIndex++)
        {
            const Ptr<GraphicsEntity>& curEntity = visLinks[linkIndex];
            n_assert(GraphicsEntity::ModelType == curEntity->GetType());
            const Ptr<ModelEntity>& modelEntity = curEntity.downcast<ModelEntity>();
            visResolver->AttachVisibleModelInstance(modelEntity->GetModelInstance());
        }
        visResolver->EndResolve();

        // prepare shadow buffer render target for rendering and 
        // patch current shadow buffer render target into the frame shader
        Math::rectangle<uint> resolveRect;
        resolveRect.left   = lightIndex * this->shadowBuffer->GetWidth();
        resolveRect.right  = resolveRect.left + this->shadowBuffer->GetWidth();
        resolveRect.top    = 0;
        resolveRect.bottom = this->shadowBuffer->GetHeight();
        this->shadowBuffer->SetResolveRect(resolveRect);
        this->finalPostEffect->SetRenderTarget(this->shadowBuffer);

        // render the resolved model node instances into the main shadow buffer
        this->frameShader->Render();

        // patch shadow buffer and shadow buffer uv offset into the light source  
        // uvOffset.xy is offset
        // uvOffset.zw is modulate
        // also moves projection space coords into uv space
        float shadowBufferHoriPixelSize = 1.0f / this->shadowBuffer->GetResolveTextureWidth();
        Math::float4 uvOffset;
        uvOffset.x() = float(lightIndex) / float(MaxNumShadowLights);
        uvOffset.y() = 0.0f;
        uvOffset.z() = (1.0f - shadowBufferHoriPixelSize) / float(MaxNumShadowLights);
        uvOffset.w() = 1.0f;
        lightEntity->SetShadowBufferUvOffsetAndScale(uvOffset);
    }

    // generate mip levels for the shadow buffer
    this->shadowBuffer->GenerateMipLevels();

    // restore original view and projection transforms
    transDev->SetViewTransform(origView);
    transDev->SetProjTransform(origProj);
}

} // namespace Lighting
